// (C) 2021 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.


`timescale 1 ns / 1 ps
`default_nettype none

`define USE_LVDS

module smbus_relay_slv #(
    parameter CLOCK_PERIOD_PS       = 10000,        // period of the input 'clock'
    parameter BUS_SPEED_KHZ         = 100          // bus speed of the slowest device on the slave SMBus interface
) (
    input  logic                           clock,              // master clock for this block
    input  logic                           i_resetn,           // master reset, must be de-asserted synchronously with clock
    
	//ioc frames
	output logic [3:0]                     ioc_frame_o,
	input  logic [3:0]                     ioc_frame_i,
    
    input  wire                            ia_master_scl,      // asynchronous input from the SCL pin of the master interface
    output logic                           o_master_scl_oe,    // when asserted, drive the SCL pin of the master interface low
    input  wire                            ia_master_sda,      // asynchronous input from the SDA pin of the master interface
    output logic                           o_master_sda_oe,    // when asserted, drive the SDA pin of the master interface low
    
    output                                 stretch_timeout
);
    
	localparam int SCLK_HOLD_MIN_COUNT =
        BUS_SPEED_KHZ == 100 ? ((5000000 + CLOCK_PERIOD_PS - 1) / CLOCK_PERIOD_PS) - 6 :
        BUS_SPEED_KHZ == 400 ? ((1300000 + CLOCK_PERIOD_PS - 1) / CLOCK_PERIOD_PS) - 6 :
                               (( 500000 + CLOCK_PERIOD_PS - 1) / CLOCK_PERIOD_PS) - 6  ;

    localparam int SCLK_HOLD_COUNTER_BIT_WIDTH = $clog2(SCLK_HOLD_MIN_COUNT)+1;             // counter will count from SCLK_HOLD_MIN_COUNT-1 downto -1
    
	localparam int NOISE_FILTER_MIN_CLOCK_CYCLES = ((50000 + CLOCK_PERIOD_PS - 1) / CLOCK_PERIOD_PS);
    localparam int NUM_FILTER_REGS = NOISE_FILTER_MIN_CLOCK_CYCLES >= 2 ? NOISE_FILTER_MIN_CLOCK_CYCLES : 2;

	localparam int SETUP_TIME_COUNT = 
		BUS_SPEED_KHZ == 100 ? 8:
        BUS_SPEED_KHZ == 400 ? 4 :4;
		
	localparam int SCL_HIGH_TIME_COUNT = 
		BUS_SPEED_KHZ == 100 ? 120:
        BUS_SPEED_KHZ == 400 ? 20 :20;
				
 	enum logic [3:0] {
	idle,
	start,
	start_rcv,
	stop,
	stop_rcv,
	bit_rcv,
	data_0,
	data_1
	} ioc_frame_local, ioc_frame_remote;
	
	`ifdef USE_LVDS
	
	//TX
	reg lvds_sample_count_en;
	reg [4:0] lvds_sample_count;
	
	always@(posedge clock or negedge i_resetn)
	if(!i_resetn) begin
		lvds_sample_count <= 'b0;
		lvds_sample_count_en <= 'b0;
		ioc_frame_o <= 'b0;
	end
  	else begin
		if(lvds_sample_count == 5'd11) begin
			lvds_sample_count_en <= 'b0;
			lvds_sample_count <= 'b0;
			ioc_frame_o <= 'b0;
		end
		else if(ioc_frame_local != idle) begin
			lvds_sample_count_en <= 1'b1;
			case(ioc_frame_local)
				start_rcv: ioc_frame_o <= 4'h2;
				stop_rcv:  ioc_frame_o <= 4'h4;
				bit_rcv:   ioc_frame_o <= 4'h5;
				data_0:    ioc_frame_o <= 4'h6;
				data_1:    ioc_frame_o <= 4'h7;
			endcase
		end
		else if(lvds_sample_count_en) begin
			lvds_sample_count <= lvds_sample_count + 1'b1;
		end
	end

	//Rx
	always@(*)
		case(ioc_frame_i)
		4'h0: ioc_frame_remote = idle;
		4'h1: ioc_frame_remote = start;
		4'h3: ioc_frame_remote = stop;
		4'h5: ioc_frame_remote = bit_rcv;
		4'h6: ioc_frame_remote = data_0;
		4'h7: ioc_frame_remote = data_1;
		default: ioc_frame_remote = idle;
	endcase
	
	
	`else

		assign ioc_frame_remote = ioc_frame_i;
		
		always @ (*) begin
			ioc_frame_o = ioc_frame_local;
		end
		
	`endif
	
    ///////////////////////////////////////
    // Synchronize asynchronous SMBus input signals to clock and detect edges and start/stop conditions
    ///////////////////////////////////////

    logic master_scl;
    logic master_sda;
    
    logic master_scl_posedge;
    logic master_scl_negedge;
    logic master_sda_posedge;
    logic master_sda_negedge;

    // SDA signals are delayed by 1 extra clock cycle, to provide a small amount of hold timing when sampling SDA on the rising edge of SCL
    async_input_filter #(
        .NUM_METASTABILITY_REGS (2),
        .NUM_FILTER_REGS        (NUM_FILTER_REGS)
    ) sync_master_scl_inst (
        .clock                  ( clock ),
        .ia_async_in            ( ia_master_scl ),
        .o_sync_out             ( master_scl ),
        .o_rising_edge          ( master_scl_posedge ),
        .o_falling_edge         ( master_scl_negedge )
    );

    async_input_filter #(
        .NUM_METASTABILITY_REGS (2),
        .NUM_FILTER_REGS        (NUM_FILTER_REGS+1)
    ) sync_master_sda_inst (
        .clock                  ( clock ),
        .ia_async_in            ( ia_master_sda ),
        .o_sync_out             ( master_sda ),
        .o_rising_edge          ( master_sda_posedge ),
        .o_falling_edge         ( master_sda_negedge )
    );
    
    
    // detect start and stop conditions on the master bus, asserted 1 clock cycle after the start/stop condition actually occurs
    logic master_start;
    logic master_stop;
    logic master_scl_dly;       // delayed version of master_scl by 1 clock cycle
    
    always_ff @(posedge clock or negedge i_resetn) begin
        if (!i_resetn) begin
            master_start        <= '0;
            master_stop         <= '0;
            master_scl_dly      <= '0;
        end else begin
            master_start        <= master_scl & master_sda_negedge;      // falling edge on SDA while SCL high is a start condition
            master_stop         <= master_scl & master_sda_posedge;      // rising edge on SDA while SCL high is a stop condition
            master_scl_dly      <= master_scl;
        end
    end
    

    ///////////////////////////////////////
    // Track the 'phase' of the master SMBus
    ///////////////////////////////////////
    
    enum {
        SMBMASTER_STATE_IDLE                ,
        SMBMASTER_STATE_START               ,
        SMBMASTER_STATE_MASTER_ADDR         ,
        SMBMASTER_STATE_SLAVE_ADDR_ACK      ,
        SMBMASTER_STATE_MASTER_CMD          ,
        SMBMASTER_STATE_SLAVE_CMD_ACK       ,
        SMBMASTER_STATE_MASTER_WRITE        ,
        SMBMASTER_STATE_SLAVE_WRITE_ACK     ,
        SMBMASTER_STATE_SLAVE_READ          ,
        SMBMASTER_STATE_MASTER_READ_ACK     ,
        SMBMASTER_STATE_STOP                //,
    }                               master_smbstate;                // current state of the master SMBus


	
	
    logic [3:0]                     master_bit_count;               // number of bits received in the current byte (starts at 0, counts up to 8)
    logic                           clear_master_bit_count;         // reset the master_bit_count to 0 when asserted
    logic [7:0]                     master_byte_in;                 // shift register to store incoming bits, shifted in starting at position 0 and shifting left (so first bit ends up as the msb)
    logic                           command_rd_wrn;                 // captured during the slave address phase, indicates if the current command is a READ (1) or WRITE (0) command
    logic                           master_read_nack;               // capture the ack/nack status from the master after a read data byte has been sent
    logic                           master_read_nack_valid;         // used to ensure master_read_nack is only captured on the first rising edge of clock after read data has been sent
    logic                           master_triggered_start;         // used to indicates that the next start condition is a repeated start

    
    enum {
        RELAY_STATE_IDLE                                    ,
		RELAY_STATE_RESTART_WAIT_SDA_HIGH                   ,
		RELAY_STATE_RESTART_WAIT_SCL_HIGH                   ,
		RELAY_STATE_RESTART_SCL_HIGH_TIMEOUT                ,
		RELAY_STATE_START_WAIT_SDA_LOW                      ,
        RELAY_STATE_START_WAIT_TIMEOUT                      ,
        RELAY_STATE_START_WAIT_SCL_LOW                     	,
        RELAY_STATE_STOP_WAIT_SSCL_LOW                      ,
        RELAY_STATE_STOP_SSCL_LOW_WAIT_TIMEOUT              ,
        RELAY_STATE_STOP_SSDA_LOW_WAIT_SSCL_HIGH            ,
        RELAY_STATE_STOP_SSDA_LOW_SSCL_HIGH_WAIT_TIMEOUT    ,
        RELAY_STATE_STOP_SSCL_HIGH_WAIT_SSDA_HIGH           ,
        RELAY_STATE_STOP_SSDA_HIGH_SSCL_HIGH_WAIT_TIMEOUT   ,
        RELAY_STATE_STOP_RESET_TIMEOUT_COUNTER              ,
        RELAY_STATE_STOP_WAIT_SECOND_TIMEOUT                ,
        RELAY_STATE_STOP_SECOND_RESET_TIMEOUT_COUNTER       ,
        // in all 'MTOS' (Master to Slave) states, the master is driving the SDA signal to the slave, so SDA must be relayed from the master bus to the slave bus (or sometimes a 'captured' version of SDA)
        RELAY_STATE_MTOS_WAIT_EVENT                     	,
        RELAY_STATE_MTOS_SCL_LOW_WAIT_SETUP_TIMEOUT              	,
        RELAY_STATE_MTOS_SCL_HIGH_WAIT_TIMEOUT             	,
        RELAY_STATE_MTOS_WAIT_SCL_HIGH                     	,
        RELAY_STATE_MTOS_WAIT_SCL_LOW                      	,
        // in all 'STOM' (Slave to Master) states, the slave is driving the SDA signal to the master, so SDA must be relayed from the slave bus to the master bus (or sometimes a 'captured' version of SDA)
        RELAY_STATE_STOM_WAIT_SCL_HIGH                     	,
        RELAY_STATE_STOM_SCL_HIGH_WAIT_TIMEOUT					,
		RELAY_STATE_STOM_WAIT_SCL_LOW						,
        RELAY_STATE_STOM_SCL_LOW_WAIT_TIMEOUT              	,
		RELAY_STATE_STOM_WAIT_BIT_RCV           			,
        RELAY_STATE_STOM_BTYE_DONE            				
    }                                           relay_state;                    // current state of the relay between the master and slave busses
    
	
	logic [SCLK_HOLD_COUNTER_BIT_WIDTH-1:0]     slave_scl_hold_count;           // counter to determine how long to hold the slave scl signal low/high, counts down to -1 then waits to be restarted (so msb=1 indicates the counter has timed out)
    logic                                       slave_scl_hold_count_restart;   // combinatorial signal, reset the slave_scl_hold_count counter and start a new countdown
    logic                                       scl_hold_count_timeout;   // combinatorial signal (copy of msb of slave_scl_hold_count), indicates the counter has reached it's timeout value and is not about to be restarted
    
	logic [19:0]                                event_waiting_count;
	logic                                       event_waiting_count_restart;
	
	logic [4:0]                                 setup_timeout;
    logic [6:0]                                 scl_high_count_timeout;
    
    always_ff @(posedge clock or negedge i_resetn) begin
        if (!i_resetn) begin
        
            master_smbstate             <= SMBMASTER_STATE_IDLE;
            master_bit_count            <= 4'h0;
            clear_master_bit_count      <= '0;
            master_byte_in              <= 8'h00;
            command_rd_wrn              <= '0;
            master_read_nack            <= '0;
            master_read_nack_valid      <= '0;
            master_triggered_start      <= '0;
			ioc_frame_local             <= idle;
        end
		else if(event_waiting_count == 20'b0) begin
		    master_smbstate             <= SMBMASTER_STATE_IDLE;
            master_bit_count            <= 4'h0;
            clear_master_bit_count      <= '0;
            master_byte_in              <= 8'h00;
            command_rd_wrn              <= '0;
            master_read_nack            <= '0;
            master_read_nack_valid      <= '0;
            master_triggered_start      <= '0;
			ioc_frame_local             <= idle;
		end
		else begin
        
            case ( master_smbstate )
                // This is the reset state.  Wait here until a valid START condition is detected on the master smbus
                SMBMASTER_STATE_IDLE: begin
					if (ioc_frame_remote == start) begin                  // only way to leave the idle state is if we detect a 'start' condition
						master_smbstate <= SMBMASTER_STATE_START;
					end
					ioc_frame_local        <= idle;
					clear_master_bit_count <= '1;                               // hold the bit counter at 0 until we have exited the idle state
				end
                
                // START state
                SMBMASTER_STATE_START: begin
						if(master_scl_negedge) begin
							master_smbstate <= SMBMASTER_STATE_MASTER_ADDR;
							ioc_frame_local <= start_rcv;
						end
						else begin
							ioc_frame_local <= idle;
						end
                    clear_master_bit_count <= '1;
                end

                // MASTER_ADDR state
                // receive the 7 bit slave addres and the read/write bit
                // leave this state when all 8 bits have been received and the clock has been driven low again by the master
                SMBMASTER_STATE_MASTER_ADDR: begin
					if (ioc_frame_remote == stop || ioc_frame_remote == start) begin
						master_smbstate <= SMBMASTER_STATE_STOP;               
						ioc_frame_local <= idle;
                    end 
					else begin
						if (master_scl_negedge == 1'b1 && master_bit_count == 4'h8) begin   // we have received all 8 data bits, time for the ACK
								master_smbstate <= SMBMASTER_STATE_SLAVE_ADDR_ACK;
						end
						if(master_scl_negedge) begin
							ioc_frame_local <= bit_rcv;
						end
						else begin
							ioc_frame_local <= idle;
						end
                        clear_master_bit_count <= '0;
                    end
                end
				
                // SLAVE_ADDR_ACK state
                SMBMASTER_STATE_SLAVE_ADDR_ACK: begin
					if (ioc_frame_remote == stop || ioc_frame_remote == start) begin
						master_smbstate <= SMBMASTER_STATE_STOP;
						ioc_frame_local <= idle;
                    end 
					else begin
                        if (master_scl_negedge) begin                          
                            if (master_sda) begin 
								master_smbstate <= master_smbstate; //wait for stop
                            end 
							else if (command_rd_wrn) begin
								master_smbstate <= SMBMASTER_STATE_SLAVE_READ;              // this is a read command, start sending data back from slave
							end 
							else begin
								master_smbstate <= SMBMASTER_STATE_MASTER_CMD;              // receive the command on the next clock cycle
							end
                        end
						
						if(master_scl_negedge) begin
							if(master_sda == 0) begin
								ioc_frame_local <= data_0;
							end
							else begin
								ioc_frame_local <= data_1;
							end
						end
						else begin
							ioc_frame_local <= idle;
						end
                    end
                    clear_master_bit_count <= '1;                               
                end
                        
                // MASTER_CMD state
                // receive the 8 bit command (the first data byte after the address is called the 'command')
                // always enter this state after an scl falling edge
                // leave this state when all 8 bits have been received and the clock has been driven low again by the master
                SMBMASTER_STATE_MASTER_CMD: begin
					if (ioc_frame_remote == stop || ioc_frame_remote == start) begin                      // unexpected stop/start condition, ignore further master bus activity until we can issue a 'stop' condition on the slave bus
						master_smbstate <= SMBMASTER_STATE_STOP;
						ioc_frame_local <= idle;
                    end 
					else begin
                        if (master_bit_count == 4'h8 && master_scl_negedge == 1'b1) begin   // we have received all 8 data bits, time for the ACK
							master_smbstate <= SMBMASTER_STATE_SLAVE_CMD_ACK;
                        end
						
						if(master_scl_negedge) begin
							ioc_frame_local <= bit_rcv;
						end
						else begin
							ioc_frame_local <= idle;
						end						
                    end
                    clear_master_bit_count <= '0;
                end

                // SLAVE_CMD_ACK or SLAVE_WRITE_ACK state
                SMBMASTER_STATE_SLAVE_CMD_ACK,
                SMBMASTER_STATE_SLAVE_WRITE_ACK: begin
					if (ioc_frame_remote == stop || ioc_frame_remote == start) begin                      // unexpected stop/start condition, ignore further master bus activity until we can issue a 'stop' condition on the slave bus
						master_smbstate <= SMBMASTER_STATE_STOP;
						ioc_frame_local <= idle;
                    end 
					else begin
                        if (master_scl_negedge) begin
                            if (master_sda) begin                           
								master_smbstate <= master_smbstate; //wait for stop
                            end 
							else begin
								master_smbstate <= SMBMASTER_STATE_MASTER_WRITE;
                            end
                        end
						
						if(master_scl_negedge) begin
							if(master_sda == 0) begin
								ioc_frame_local <= data_0;
							end
							else begin
								ioc_frame_local <= data_1;
							end
						end
						else begin
							ioc_frame_local <= idle;
						end
						
                    end
                    clear_master_bit_count <= '1;
                end

                // MASTER_WRITE state
                // receive a byte to write to the slave device
                // always enter this state after an scl falling edge
                SMBMASTER_STATE_MASTER_WRITE: begin
					if (ioc_frame_remote == stop) begin                                      // unexpected stop condition, ignore further master bus activity until we can issue a 'stop' condition on the slave bus
						master_smbstate <= SMBMASTER_STATE_STOP;
						ioc_frame_local <= idle;
                    end else begin
						if (ioc_frame_remote == start) begin                                // restart condition received, allow the command to proceed
							master_smbstate <= SMBMASTER_STATE_START;
							ioc_frame_local <= idle;
                        end 
						else begin
							if (master_scl_negedge == 1'b1) begin
								if (master_bit_count == 4'h8 && master_scl_negedge == 1'b1) begin   // we have received all 8 data bits for a whitelisted command, time for the ACK
									master_smbstate <= SMBMASTER_STATE_SLAVE_WRITE_ACK;
								end
								ioc_frame_local <= bit_rcv;
							end
							else begin
								ioc_frame_local <= idle;
							end

                        end
                    end
                    clear_master_bit_count <= '0;
                end

                // SLAVE_READ state
                // receive a byte from the slave device and send it to the master
                // always enter this state after an scl falling edge
                SMBMASTER_STATE_SLAVE_READ: begin
					if (ioc_frame_remote == stop || ioc_frame_remote == start) begin                      // unexpected stop/start condition, ignore further master bus activity until we can issue a 'stop' condition on the slave bus
						master_smbstate <= SMBMASTER_STATE_STOP;
						ioc_frame_local <= idle;
                    end 
					else begin                       							
						if (master_scl_negedge == 1'b1 && master_bit_count == 4'h8) begin   // we have sent all 8 data bits, time for the ACK (from the master)
							master_smbstate <= SMBMASTER_STATE_MASTER_READ_ACK;
						end
											
						if(master_scl_negedge == 1'b1) begin
							if(master_sda == 1'b0)
								ioc_frame_local <= data_0;
							else
								ioc_frame_local <= data_1;	
						end
						else begin
							ioc_frame_local <= idle;
						end						
                    end
                    clear_master_bit_count <= '0;
                end
                
                // MASTER_READ_ACK
                // always enter this state after an scl falling edge
                // leave this state when the ack/nack bit has been received and the clock has been drivien low again by the master 
                SMBMASTER_STATE_MASTER_READ_ACK: begin
					
					if (ioc_frame_remote == stop || ioc_frame_remote == start) begin                      // unexpected stop/start condition, ignore further master bus activity until we can issue a 'stop' condition on the slave bus
						master_smbstate <= SMBMASTER_STATE_STOP;
						ioc_frame_local <= idle;
                    end 
					else begin
                        if (master_scl_negedge) begin
                            if (~master_read_nack) begin                        // if we received an ack (not a nack) from the master, continue reading data from slave
								master_smbstate <= SMBMASTER_STATE_SLAVE_READ;
                            end 
							else begin                                      // on a nack, send a stop condition on the slave bus and wait for a stop on the master bus
								master_smbstate <= master_smbstate;
                            end
							ioc_frame_local <= bit_rcv;
                        end
						else begin
							ioc_frame_local <= idle;
						end
                    end
                    clear_master_bit_count <= '1;
                end
				
                // STOP
                // Enter this state to indicate a STOP condition should be sent to the slave bus
                SMBMASTER_STATE_STOP: begin
					if (relay_state == RELAY_STATE_IDLE) begin
						master_smbstate <= SMBMASTER_STATE_IDLE;
						ioc_frame_local <= stop_rcv;
                    end
					else begin
						ioc_frame_local <= idle;
					end
                    clear_master_bit_count <= '1;
				end
                
                default: begin // illegal state, should never get here
                    master_smbstate <= SMBMASTER_STATE_IDLE;
                    clear_master_bit_count <= '1;
                    ioc_frame_local <= idle;
                end
                
            endcase  

            // counter for bits received on the master bus
            // the master SMBus state machine will clear the counter at the appropriate times, otherwise it increments on every master scl rising edge
            if (clear_master_bit_count) begin       // need to reset the counter on a start condition to handle the repeated start (simpler than assigning clear signal in this one special case)
                master_bit_count <= 4'h0;
            end else begin
                if (master_scl_posedge) begin
                    master_bit_count <= master_bit_count + 4'b0001;
                end
            end
            
            // shift register to store incoming bits from the master bus
            // shifts on every clock edge regardless of bus state, rely on master_smbstate and master_bit_count to determine when a byte is fully formed
            if (master_scl_posedge) begin
                master_byte_in[7:0] <= {master_byte_in[6:0], master_sda};
            end
            
			if ( (master_smbstate == SMBMASTER_STATE_MASTER_ADDR) && (master_bit_count == 4'h8) ) begin  // 8th bit is captured on the next clock cycle, so that's when it's safe to check the full byte contents
                command_rd_wrn <= master_byte_in[0];
            end			
			
			
            // capture the read data ACK/NACK from the master after read data has been sent
            // make sure to only capture ack/nack on the first rising edge of SCL using the master_read_nack_valid signal
            if (master_smbstate == SMBMASTER_STATE_MASTER_READ_ACK) begin
                if (master_scl_posedge) begin
                    if (!master_read_nack_valid) begin
                        master_read_nack <= master_sda;
                    end
                    master_read_nack_valid <= '1;
                end
            end else begin
                master_read_nack_valid <= '0;
                master_read_nack <= '1;
            end
            
            // repeated start can only be used to switch from write mode to read mode in SMBus protocol
            if (master_smbstate == SMBMASTER_STATE_MASTER_CMD) begin
                master_triggered_start <= '1;
            end else if ((master_smbstate == SMBMASTER_STATE_STOP) || (master_smbstate == SMBMASTER_STATE_IDLE)) begin            
                master_triggered_start <= '0;
            end                        
        end
        
    end

    
    ///////////////////////////////////////
    // Determine the state of the relay, and drive the slave and master SMBus signals based on the state of the master and slave busses
    ///////////////////////////////////////

    always_ff @(posedge clock or negedge i_resetn) begin
        if (!i_resetn) begin

            relay_state                     <= RELAY_STATE_IDLE;
            o_master_scl_oe                 <= '0;
            o_master_sda_oe                 <= '0;
            slave_scl_hold_count            <= {SCLK_HOLD_COUNTER_BIT_WIDTH{1'b0}};
			setup_timeout                   <= 5'b0;
            scl_high_count_timeout          <= 7'b0;
            
        end 
		else begin
        
            case ( relay_state )
            
                // IDLE state
                // waiting for a start condition on the master busses
                RELAY_STATE_IDLE: begin
                    if (master_smbstate == SMBMASTER_STATE_START) begin
                        relay_state <= RELAY_STATE_START_WAIT_SDA_LOW;
                    end
                    o_master_scl_oe <= '0;
                    o_master_sda_oe <= '0;
                end

				//RELAY_STATE_RESTART_WAIT_SDA_HIGH
				//a restart condition is detect and drive sda high when scl is low
				RELAY_STATE_RESTART_WAIT_SDA_HIGH: begin
					if(master_sda) begin
						relay_state <= RELAY_STATE_RESTART_WAIT_SCL_HIGH;
					end
					o_master_scl_oe <= '0;
					o_master_sda_oe <= '0;
				end
				
				//Just in case SCL is low
                RELAY_STATE_RESTART_WAIT_SCL_HIGH: begin
					if(master_scl) begin
						relay_state <= RELAY_STATE_RESTART_SCL_HIGH_TIMEOUT;
					end
					o_master_scl_oe <= '0;
					o_master_sda_oe <= '0;
				end

				//Just in case SCL is low
                RELAY_STATE_RESTART_SCL_HIGH_TIMEOUT: begin
					if(scl_hold_count_timeout) begin
						relay_state <= RELAY_STATE_START_WAIT_SDA_LOW;
					end
					o_master_scl_oe <= '0;
					o_master_sda_oe <= '0;
				end
				
				//scl is high
				//wait for sda low
                RELAY_STATE_START_WAIT_SDA_LOW: begin
					if(~master_sda) begin
						relay_state <= RELAY_STATE_START_WAIT_TIMEOUT;
					end
					o_master_scl_oe <= '0;
					o_master_sda_oe <= '1;
				end

				// START_WAIT_TIMEOUT
                // Start condition has been received on the bus, wait for a timeout
                RELAY_STATE_START_WAIT_TIMEOUT: begin
					if (scl_hold_count_timeout) begin
                        relay_state <= RELAY_STATE_START_WAIT_SCL_LOW;
                    end
					o_master_scl_oe <= '0;
					o_master_sda_oe <= '1;
				end

                // START_WAIT_MSCL_LOW
                // Start condition has been received on the master bus
                // Continue to drive a start condition on the slave bus (SDA low while SCL is high) and wait for master scl to go low
                // If master SCL is low during this state, continue to hold it there to prvent further progress on the master bus until the slave bus can 'catch up'
                RELAY_STATE_START_WAIT_SCL_LOW: begin
                    if (~master_scl) begin
						relay_state <= RELAY_STATE_MTOS_WAIT_EVENT;     // after a start, the master is driving the bus
                    end else if (master_smbstate == SMBMASTER_STATE_STOP) begin        // stop right after start may not be legal, but safter to handle this case anyway
						relay_state <= RELAY_STATE_STOP_SSDA_LOW_SSCL_HIGH_WAIT_TIMEOUT;
						$display("Relay state: Stop right after start may not be legal ");
                    end
					o_master_scl_oe <= '1;
					o_master_sda_oe <= '1;                
				end
                
                // RELAY_STATE_MTOS_WAIT_EVENT
                // Capture remote event and setup sda
				// scl is low in this state 
                RELAY_STATE_MTOS_WAIT_EVENT: begin
                    if (master_smbstate == SMBMASTER_STATE_START) begin
						relay_state <= RELAY_STATE_RESTART_WAIT_SDA_HIGH;
                    end 
					else if (master_smbstate == SMBMASTER_STATE_STOP ) begin
                        if (~master_byte_in[0]) begin       // we know slave SCL is high, if slave SDA is low proceed to the appropriate phase of the STOP states
							relay_state <= RELAY_STATE_STOP_SSDA_LOW_SSCL_HIGH_WAIT_TIMEOUT;
                        end 
						else begin                      // we need to first drive slave SCL low so we can drive slave SDA low then proceed to create the STOP condition
							relay_state <= RELAY_STATE_STOP_WAIT_SSCL_LOW;
                        end                    
					end
					else begin
						if(event_waiting_count == 20'b0) begin //timeout
							relay_state <= RELAY_STATE_IDLE;								
							o_master_scl_oe <= '0;             // we know master SCL is low here, we continue to hold it low
							o_master_sda_oe <= '0;              // drive sda from the slave bus to the master bus							
						end
						else if(ioc_frame_remote == data_0) begin
							relay_state <= RELAY_STATE_MTOS_SCL_LOW_WAIT_SETUP_TIMEOUT;								
							o_master_scl_oe <= '1;             // we know master SCL is low here, we continue to hold it low
							o_master_sda_oe <= '1;              // drive sda from the slave bus to the master bus
						end
						else if(ioc_frame_remote == data_1) begin
							relay_state <= RELAY_STATE_MTOS_SCL_LOW_WAIT_SETUP_TIMEOUT;								
							o_master_scl_oe <= '1;             // we know master SCL is low here, we continue to hold it low
							o_master_sda_oe <= '0;              // drive sda from the slave bus to the master bus
						end
						else begin
							o_master_scl_oe <= '1;             // we know master SCL is low here, we continue to hold it low
							o_master_sda_oe <= o_master_sda_oe;
						end
					end

				end

				//RELAY_STATE_MTOS_SCL_LOW_WAIT_SETUP_TIMEOUT
				//Sda is ready
				//wait for scl low timeout
                RELAY_STATE_MTOS_SCL_LOW_WAIT_SETUP_TIMEOUT: begin
                    if(setup_timeout == SETUP_TIME_COUNT) begin
						setup_timeout <= 5'b0;
						relay_state <= RELAY_STATE_MTOS_WAIT_SCL_HIGH;
                    end
					else begin
						setup_timeout <= setup_timeout + 5'd1;
					end
                    o_master_scl_oe <= '1;             // we know master SCL is low here, we continue to hold it low
                    o_master_sda_oe <= o_master_sda_oe; 
				end
                // RELAY_STATE_MTOS_WAIT_SCL_HIGH
                // sda is ready
				// wait for scl go high
                RELAY_STATE_MTOS_WAIT_SCL_HIGH: begin
					if(master_scl) begin
						relay_state <= RELAY_STATE_MTOS_SCL_HIGH_WAIT_TIMEOUT;
                    end
                    o_master_scl_oe <= '0;
                    o_master_sda_oe <= o_master_sda_oe; 
				end

                // MTOS_SSCL_HIGH_WAIT_TIMEOUT
                // Master is driving the bus, SDA from the master bus to the slave bus
                // Slave SCL is high, wait for a timeout before allowing it to be driven low
                // If master SCL goes low again during this state, hold it there to prvent further progress on the master bus until the slave bus can 'catch up'
                RELAY_STATE_MTOS_SCL_HIGH_WAIT_TIMEOUT: begin
                    if (scl_hold_count_timeout) begin
                        if(master_smbstate == SMBMASTER_STATE_START) begin
							relay_state <= RELAY_STATE_RESTART_WAIT_SDA_HIGH;
						end
						else begin
							relay_state <= RELAY_STATE_MTOS_WAIT_SCL_LOW;
						end
                    end
                    o_master_scl_oe <= '0;             // if master SCL goes low, we hold it low
                    o_master_sda_oe <= o_master_sda_oe; 
				end

                // MTOS_WAIT_MSCL_LOW
                // Master is driving the bus, SDA from the master bus to the slave bus
                // Wait for master SCL to go low, then hold it low (clockstretch) and proceed to state where we drive the slave scl low
                RELAY_STATE_MTOS_WAIT_SCL_LOW: begin
                     if (~master_scl_dly) begin      // need to look at a delayed version of master scl, to give master_smbstate state machine time to update
                        // check if we are in a state where the slave is driving data back to the master bus
                        if (    (master_smbstate == SMBMASTER_STATE_SLAVE_ADDR_ACK) ||
                                 (master_smbstate == SMBMASTER_STATE_SLAVE_CMD_ACK) ||
                                 (master_smbstate == SMBMASTER_STATE_SLAVE_WRITE_ACK) ||
                                 (master_smbstate == SMBMASTER_STATE_SLAVE_READ)
                        ) begin
							relay_state <= RELAY_STATE_STOM_SCL_LOW_WAIT_TIMEOUT;
                        end 
						else begin
							//keep master send
							relay_state <= RELAY_STATE_MTOS_WAIT_EVENT;
                        end
                     end
                    o_master_scl_oe <= '1;             // if master SCL is low, we hold it low
                    o_master_sda_oe <= o_master_sda_oe; 
				end
				
                // RELAY_STATE_STOM_SCL_LOW_WAIT_TIMEOUT
                // wait for slave to prepare sda. sda should be ready before ***scl_hold_count_timeout***
				// so need to sync with slave bus speed. (Chen is helping on this)
				// scl is low
                RELAY_STATE_STOM_SCL_LOW_WAIT_TIMEOUT: begin
                    if (scl_hold_count_timeout) begin
						relay_state <= RELAY_STATE_STOM_WAIT_SCL_HIGH;
                    end
                    o_master_scl_oe <= '1;             // we know master SCL is low here, we continue to hold it low
                    o_master_sda_oe <= '0;              // drive sda from the slave bus to the master bus
				end

                // RELAY_STATE_STOM_WAIT_SCL_HIGH
                // data is ready on posedge
                RELAY_STATE_STOM_WAIT_SCL_HIGH: begin
                    if (master_scl) begin
                        if(scl_high_count_timeout == SCL_HIGH_TIME_COUNT) begin
                            relay_state <= RELAY_STATE_STOM_WAIT_SCL_LOW;
                            scl_high_count_timeout <= 7'b0;
                        end
                        else
                            scl_high_count_timeout <= scl_high_count_timeout + 7'd1;
                    end
                    o_master_scl_oe <= '0;             // we know master SCL is low here, we continue to hold it low
                    o_master_sda_oe <= '0;              // drive sda from the slave bus to the master bus
				end

                // RELAY_STATE_STOM_WAIT_SCL_LOW
                // data is ready on posedge
                RELAY_STATE_STOM_WAIT_SCL_LOW: begin
                    if (~master_scl) begin
						relay_state <= RELAY_STATE_STOM_WAIT_BIT_RCV;
                    end
                    o_master_scl_oe <= '1;             // we know master SCL is low here, we continue to hold it low
                    o_master_sda_oe <= '0;              // drive sda from the slave bus to the master bus
				end

                // RELAY_STATE_STOM_WAIT_BIT_RCV
                // master receive the bit
				// hold scl low
                RELAY_STATE_STOM_WAIT_BIT_RCV: begin
						if(event_waiting_count == 20'b0) begin //timeout
							relay_state <= RELAY_STATE_IDLE;								
							o_master_scl_oe <= '0;             // we know master SCL is low here, we continue to hold it low
							o_master_sda_oe <= '0;              // drive sda from the slave bus to the master bus							
						end
						else if(ioc_frame_remote == bit_rcv) begin;
							relay_state <= RELAY_STATE_STOM_BTYE_DONE;
						end
						else begin
							o_master_scl_oe <= '1;
							o_master_sda_oe <= '0;     // drive master sda with the value captured on the rising edge of slave scl
						end
				end
 
                // STOM_SSCL_HIGH_WAIT_MSCL_LOW
                // Slave is driving data, SDA from the slave bus to the master
                // Slave and master SCL are high, we are waiting for master SCL to go low
                RELAY_STATE_STOM_BTYE_DONE: begin

                        if (~master_scl_dly) begin
                            if ( master_smbstate == SMBMASTER_STATE_SLAVE_READ ) begin      // only in the SLAVE_READ state do we have two SMBus cycles in a row where the slave drives data to the master
								relay_state <= RELAY_STATE_STOM_SCL_LOW_WAIT_TIMEOUT;
                            end else begin
								relay_state <= RELAY_STATE_MTOS_WAIT_EVENT;
                            end
                        end
                    o_master_scl_oe <= '1;
                    o_master_sda_oe <= '0;     // drive master sda with the value captured on the rising edge of slave scl
                end

                // STOP_WAIT_SSCL_LOW
                // Sending a stop condition on the slave bus
                // Drive slave SCL low, wait to see it has gone low before driving SDA low (which happens in the next state)
                // Clockstretch the master bus if master SCL is driven low, to prevent the master bus from getting 'ahead' of the slave bus
                RELAY_STATE_STOP_WAIT_SSCL_LOW: begin
                    if (~master_scl) begin;
						relay_state <= RELAY_STATE_STOP_SSCL_LOW_WAIT_TIMEOUT;
                    end
                    o_master_scl_oe <= '1;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= o_master_sda_oe;
				end

                // STOP_SSCL_LOW_WAIT_TIMEOUT
                // Sending a stop condition on the slave bus
                // Drive slave SCL low, and drive slave SDA low (after allowing for suitable hold time in the previous state)
                // Clockstretch the master bus if master SCL is driven low, to prevent the master bus from getting 'ahead' of the slave bus
                RELAY_STATE_STOP_SSCL_LOW_WAIT_TIMEOUT: begin
                    if (scl_hold_count_timeout) begin
						relay_state <= RELAY_STATE_STOP_SSDA_LOW_WAIT_SSCL_HIGH;
                    end
                    o_master_scl_oe <= '1;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= '1;
				end

                // STOP_SSDA_LOW_WAIT_SSCL_HIGH
                // Allow slave SCL to go high, confirm it has gone high before proceeding
                // Clockstretch on the master bus if master SCL goes low
                RELAY_STATE_STOP_SSDA_LOW_WAIT_SSCL_HIGH: begin
                    if (master_scl) begin
						relay_state <= RELAY_STATE_STOP_SSDA_LOW_SSCL_HIGH_WAIT_TIMEOUT;
                    end
                    o_master_scl_oe <= '0;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= '1;
				end

                // STOP_SSDA_LOW_SSCL_HIGH_WAIT_TIMEOUT
                // Allow slave SCL to go high, then wait for a timeout before proceeding to next state
                // Clockstretch on the master bus if master SCL goes low
                RELAY_STATE_STOP_SSDA_LOW_SSCL_HIGH_WAIT_TIMEOUT: begin
                    if (scl_hold_count_timeout) begin
						relay_state <= RELAY_STATE_STOP_SSCL_HIGH_WAIT_SSDA_HIGH;
                    end
                    o_master_scl_oe <= '0;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= '1;
				end
                
                // STOP_SSCL_HIGH_WAIT_SSDA_HIGH
                // Allow slave SDA to go high, wait to observe it high before proceeding to the next state
                // This rising edge on SDA while SCL is high is what creates the 'stop' condition on the bus
                // Clockstretch on the master bus if master SCL goes low
                RELAY_STATE_STOP_SSCL_HIGH_WAIT_SSDA_HIGH: begin
                    if (master_sda) begin
						relay_state <= RELAY_STATE_STOP_SSDA_HIGH_SSCL_HIGH_WAIT_TIMEOUT;
                    end
                    o_master_scl_oe <= '0;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= '0;
                end

                // STOP_SSDA_HIGH_SSCL_HIGH_WAIT_TIMEOUT
                // Stop condition has been sent, wait for a timeout before proceeding to next state
                // Clockstretch on the master bus if master SCL goes low
                RELAY_STATE_STOP_SSDA_HIGH_SSCL_HIGH_WAIT_TIMEOUT: begin
                    if (scl_hold_count_timeout) begin
						relay_state <= RELAY_STATE_STOP_RESET_TIMEOUT_COUNTER;
                    end
                    o_master_scl_oe <= '0;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= '0;
                end

                // STOP_RESET_TIMEOUT_COUNTER
                // We need TWO timeout counters after a stop condition, this state exists just to allow the counter to reset
                RELAY_STATE_STOP_RESET_TIMEOUT_COUNTER: begin
					relay_state <= RELAY_STATE_STOP_WAIT_SECOND_TIMEOUT;
                    o_master_scl_oe <= '0;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= '0;
                end

                // STOP_WAIT_SECOND_TIMEOUT
                // Allow slave SCL to go high, then wait for a timeout before proceeding to next state
                // Clockstretch on the master bus if master SCL goes low
                RELAY_STATE_STOP_WAIT_SECOND_TIMEOUT: begin
                    if (scl_hold_count_timeout) begin
							relay_state <= RELAY_STATE_IDLE;
                    end
                    o_master_scl_oe <= '0;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= '0;
                end
                
                default: begin
                    relay_state <= RELAY_STATE_IDLE;
                    o_master_scl_oe <= '0;             // clockstretch on the master bus if master scl is driven low
                    o_master_sda_oe <= '0;
                end

            endcase
            
            // counter to determine how long to hold slave_scl low or high 
            // used when creating 'artificial' scl clock pulses on slave while clock stretching the master during ack and read data phases
            // counter counts from some positive value down to -1, thus checking the highest bit (sign bit) is adequate to determine if the count is 'done'
            if (slave_scl_hold_count_restart) begin
                slave_scl_hold_count <= SCLK_HOLD_MIN_COUNT[SCLK_HOLD_COUNTER_BIT_WIDTH-1:0] - {{SCLK_HOLD_COUNTER_BIT_WIDTH-1{1'b0}}, 1'b1}; 
            end else if (!slave_scl_hold_count[SCLK_HOLD_COUNTER_BIT_WIDTH-1]) begin        // count down to -1 (first time msb goes high) and then stop
                slave_scl_hold_count <= slave_scl_hold_count - {{SCLK_HOLD_COUNTER_BIT_WIDTH-1{1'b0}}, 1'b1};
            end

            if (!event_waiting_count_restart) begin 
				event_waiting_count <= 20'd600000; 
            end 
			else begin        // count down to -1 (first time msb goes high) and then stop
                event_waiting_count <= event_waiting_count - 20'd1;
            end
        end
        
    end

    // when the msb of slave_scl_hold_count = 1, that indicates a negative number, which means the timeout has occurred
    assign scl_hold_count_timeout = slave_scl_hold_count[SCLK_HOLD_COUNTER_BIT_WIDTH-1];
    
    assign stretch_timeout = scl_hold_count_timeout;
    
    // determine when to reset the counter based on the current relay state
    // creatre this signal with combinatorial logic so counter will be reset as we enter the next state
    // we never have two states in a row that both require this counter
    assign slave_scl_hold_count_restart = ( (relay_state == RELAY_STATE_START_WAIT_TIMEOUT) ||
											(relay_state == RELAY_STATE_RESTART_SCL_HIGH_TIMEOUT) ||
                                            (relay_state == RELAY_STATE_MTOS_SCL_LOW_WAIT_SETUP_TIMEOUT) ||
                                            (relay_state == RELAY_STATE_MTOS_SCL_HIGH_WAIT_TIMEOUT) ||
                                            (relay_state == RELAY_STATE_STOM_SCL_LOW_WAIT_TIMEOUT) ||
											(relay_state == RELAY_STATE_STOM_SCL_HIGH_WAIT_TIMEOUT) ||
                                            (relay_state == RELAY_STATE_STOM_WAIT_BIT_RCV) ||
                                            (relay_state == RELAY_STATE_STOP_SSCL_LOW_WAIT_TIMEOUT) ||
                                            (relay_state == RELAY_STATE_STOP_SSDA_LOW_SSCL_HIGH_WAIT_TIMEOUT) ||
                                            (relay_state == RELAY_STATE_STOP_SSDA_HIGH_SSCL_HIGH_WAIT_TIMEOUT) ||
                                            (relay_state == RELAY_STATE_STOP_WAIT_SECOND_TIMEOUT)
                                          ) ? '0 : '1;
                                          
	assign event_waiting_count_restart = ( (relay_state == RELAY_STATE_MTOS_WAIT_EVENT) ||
											(relay_state == RELAY_STATE_STOM_WAIT_BIT_RCV)
                                          ) ? '1 : '0;

endmodule